using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;

#if XBOX
#else
using System.Windows;
#endif

namespace Perovich.GameObjects.Helpers
{
    /// <summary>
    /// Specifies the gradient style of a produced texture.
    /// </summary>
    public enum GradientStyle
    {
        /// <summary>
        /// A linear gradient.
        /// </summary>
        Linear,
        /// <summary>
        /// A raidal gradient.
        /// </summary>
        Radial,
        /// <summary>
        /// An angle gradient.
        /// </summary>
        Angle,
        /// <summary>
        /// A squished linear gradient.
        /// </summary>
        Reflected,
        /// <summary>
        /// A square gradient.
        /// </summary>
        Square,
        /// <summary>
        /// A square gradient that has a solid rectangle in the middle.
        /// </summary>
        Border

    }

    /// <summary>
    /// Specifies the behavior of the gradient.
    /// </summary>
    public enum GradientProgression
    {
        /// <summary>
        /// y=x
        /// </summary>
        Linear,
        /// <summary>
        /// y=x^2
        /// </summary>
        SquareIn,
        /// <summary>
        /// y=x^3
        /// </summary>
        CubicIn,
        /// <summary>
        /// y=x^4
        /// </summary>
        QuarticIn,
        /// <summary>
        /// y=x^5
        /// </summary>
        QuinticIn,
        /// <summary>
        /// y=x^(1/2)
        /// </summary>
        SquareOut,
        /// <summary>
        /// y=x^(1/3)
        /// </summary>
        CubicOut,
        /// <summary>
        /// y=x^(1/4)
        /// </summary>
        QuarticOut,
        /// <summary>
        /// y=x^(1/5)
        /// </summary>
        QuinticOut
    }

    
    /// <summary>
    /// A static class containing functions to construct textures.
    /// </summary>
    public static class Textures
    {
        private static Texture2D _tex = null;

        internal static Texture2D GetSolidcolorTexture(GraphicsDevice device)
        {
            if (_tex == null)
            {
                _tex = new Texture2D(device, 1, 1, false, SurfaceFormat.Color);
                _tex.SetData<Color>(
                            new Color[] { Color.White },
                            0,
                            1
                            );
            }
            return _tex;
        }


        static Dictionary<Texture2D, Color[]> CollisionData = new Dictionary<Texture2D,Color[]>();

        public static ReadOnlyCollection<Color> GetCollisionData(Texture2D tex)
        {
            if (CollisionData.ContainsKey(tex))
            {
                return Array.AsReadOnly(CollisionData[tex]);
            }
            else
            {
                CollisionData.Add(tex, new Color[tex.Width * tex.Height]);
                tex.GetData<Color>(CollisionData[tex]);
                return Array.AsReadOnly(CollisionData[tex]);
            }
        }



        private static bool _initialized = false;

        /// <summary>
        /// Gets a value indicating if the <see cref="Textures"/> have been initialized.
        /// </summary>
        public static bool Initialized
        {
            get
            {
                return _initialized;
            }
        }

        /// <summary>
        /// Gets all the avaliable gradient styles.
        /// </summary>
        public static IEnumerable<GradientStyle> GradientStyles
        {
            get
            {
                return new GradientStyle[] { GradientStyle.Angle, GradientStyle.Linear, GradientStyle.Radial, GradientStyle.Reflected, GradientStyle.Square, GradientStyle.Border };
            }
        }
        static Interfaces.ICanHoldComponents g;
        static IGraphicsDeviceService graphics;

        /// <summary>
        /// Initializes the <see cref="Textures"/> for use.
        /// </summary>
        /// <param name="game"></param>
        public static void Initialize(Interfaces.ICanHoldComponents game)
        {
            g = game;
            graphics = g.Services.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
            if (graphics == null)
                throw new InvalidOperationException("Graphics device service not found");
            _initialized = true;
        }

        private static Thickness Border;

        private static float generatePercentage(Vector2 point, int width, int height, GradientStyle style)
        {
            switch (style)
            {
                case GradientStyle.Angle:
                    {
                        Vector2 x = point - new Vector2(width / 2, height / 2);
                        float ret = (1 - ((Vector2.Dot(Vector2.Normalize(x), Vector2.UnitX) + 1) / 2))/2;
                        if (x.Y > 0)
                            ret = 1 - ret;
                        return ret;
                    }
                case GradientStyle.Square:
                    {
                        float dist = Math.Min(point.X > (width/2) ? width - point.X : point.X, point.Y > (height/2) ? height - point.Y : point.Y);
                        float maxdist = Math.Max((width-1) / 2, (height-1) / 2);
                        return Math.Min(Math.Max(dist / maxdist, 0.0f), 1.0f);
                    }
                case GradientStyle.Border:
                    {
                        float dist = Math.Min(point.X > (width / 2) ? width - point.X : point.X, point.Y > (height / 2) ? height - point.Y : point.Y);
                        float maxdist = 0;
                        if (dist == point.X)
                        {
                            maxdist = (float)Border.Left;
                        }
                        else if (dist == point.Y)
                        {
                            maxdist = (float)Border.Top;
                        }
                        else if (dist == width - point.X)
                        {
                            maxdist = (float)Border.Right;
                        }
                        else
                        {
                            maxdist = (float)Border.Bottom;
                        }
                        return Math.Min(Math.Max(dist / maxdist, 0.0f), 1.0f);
                    }
                case GradientStyle.Linear:
                    {
                        return Math.Min(Math.Max(point.Y / height, 0.0f),1.0f);
                    }
                case GradientStyle.Radial:
                    {
                        float dist = Vector2.Distance(point, new Vector2(width / 2, height / 2));
                        float distmax = Vector2.Distance(Vector2.Zero, new Vector2(width / 2, height / 2));
                        return 1 - Math.Min(Math.Max(dist / distmax, 0.0f), 1.0f);
                    }
                case GradientStyle.Reflected:
                    {
                        return 1 - Math.Abs(Math.Min(Math.Max(point.Y / ((float)height/2), 0.0f), 2.0f) - 1);
                    }
                default:
                    throw new ArgumentException("The Gradient Style is not valid.");
            }
        }
        /// <summary>
        /// Creates a new gradient <see cref="Texture2D"/> with the provided parameters.
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="color1"></param>
        /// <param name="color2"></param>
        /// <param name="style"></param>
        /// <param name="progression"></param>
        /// <returns></returns>
        public static Texture2D CreateGradient(int width, int height, Color color1, Color color2, GradientStyle style, GradientProgression progression)
        {
            return CreateGradient(width, height, color1, color2, style, progression, new Thickness());
        }
        

        /// <summary>
        /// Creates a new gradient <see cref="Texture2D"/> with the provided parameters.
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="color1"></param>
        /// <param name="color2"></param>
        /// <param name="style"></param>
        /// <param name="progression"></param>
        /// <param name="border"></param>
        /// <returns></returns>
        public static Texture2D CreateGradient(int width, int height, Color color1, Color color2, GradientStyle style, GradientProgression progression, Thickness border)
        {
            if (!Initialized)
            {
                throw new InvalidOperationException("Must Initialize the Textures class before creating textures.");
            }
            if ((width == 0) || (height == 0))
                return null;
            Border = border;
            Texture2D tex = null;
            try
            {
                tex = new Texture2D(graphics.GraphicsDevice, width, height, false, SurfaceFormat.Color);
                Color[] bgc = new Color[height * width];
                for (int y = 0; y < height; ++y)
                {
                    for (int x = 0; x < width; ++x)
                    {
                        float percent = generatePercentage(new Vector2(x, y), width, height, style);
                        percent = applyFunction(progression, percent);
                        bgc[y * width + x] = generateGradientColor(color1, color2, percent);
                    }
                }
                tex.SetData(bgc);
                return tex;
            }
            catch (Exception)
            {
                if (tex != null)
                    tex.Dispose();
                throw;
            }
        }

        private static float applyFunction(GradientProgression gp, float value)
        {
            switch (gp)
            {
                case GradientProgression.Linear:
                    return value;
                case GradientProgression.SquareIn:
                    return value * value;
                case GradientProgression.CubicIn:
                    return value * value * value;
                case GradientProgression.QuarticIn:
                    return value * value * value * value;
                case GradientProgression.QuinticIn:
                    return value * value * value * value * value;
                case GradientProgression.SquareOut:
                    return (float)Math.Pow(value,0.5);
                case GradientProgression.CubicOut:
                    return (float)Math.Pow(value, 1.0/3);
                case GradientProgression.QuarticOut:
                    return (float)Math.Pow(value, 1.0/4);
                case GradientProgression.QuinticOut:
                    return (float)Math.Pow(value, 1.0/5);
                default:
                    throw new ArgumentException("The Gradient Progression is not valid.");
            }
        }

        private static Color generateGradientColor(Color c1, Color c2, float percent)
        {
            float c1percent = 1 - percent;
            Color c = new Color(
                (int)(c1percent * c1.R + percent * c2.R),
                (int)(c1percent * c1.G + percent * c2.G),
                (int)(c1percent * c1.B + percent * c2.B),
                (int)(c1percent * c1.A + percent * c2.A)
                );
            return c;
        }


    }
}
